////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2012 Kentaro Sekimoto  All rights reserved.
////////////////////////////////////////////////////////////////////////////

#include <tinyhal.h>
#include "..\FM3.h"

// Watchdog
#define WCCR_WCIF   0x01U
#define WCCR_WCIE   0x02U
#define WCCR_WCCK0  0x00U
#define WCCR_WCCK1  0x04U
#define WCCR_WCCK2  0x08U
#define WCCR_WCCK3  0x0CU
#define WCCR_WCOP   0x40U
#define WCCR_WCEN   0x80U

// Dual Timer
#define DtimDev_FREERUN_MODE    0U
#define DtimDev_CYCLE_MODE      1U
#define DtimDev_ONESHOT_MODE    2U
#define DtimDev_INT_ENABLE      1U
#define DtimDev_TIMER_PRE_1     0U
#define DtimDev_TIMER_PRE_16    1U
#define DtimDev_TIMER_PRE_256   2U
#define DtimDev_TIMER_SIZE_16   0U
#define DtimDev_TIMER_SIZE_32   1U
#define DtimDev_LOAD            0U
#define DtimDev_BG_LOAD         1U
#define DtimDev_IRQSTATUS_UNDERFLOW 0x01U

#define MAX_TIMER_COUNT     0xffffffffL
#define MAX_TARGET_COUNT    0xffffffffffffL
//#define MAX_TARGET_COUNT    0xffffffffffffffffL

static volatile UINT64 g_timer_count = 0L;
static volatile UINT64 g_target_count = (UINT64)MAX_TARGET_COUNT;
static volatile UINT32 g_TIMER1LOAD = MAX_TIMER_COUNT;
static volatile UINT32 g_TIMER1VALUE = 0;

UINT32 CPU_SystemClock()
{
    return SYSTEM_CLOCK_HZ;
}

UINT32 CPU_TicksPerSecond()
{
    return SLOW_CLOCKS_PER_SECOND;
}

UINT64 CPU_TicksToTime(UINT64 Ticks)
{
    Ticks *= (TEN_MHZ               /SLOW_CLOCKS_TEN_MHZ_GCD);
    Ticks /= (SLOW_CLOCKS_PER_SECOND/SLOW_CLOCKS_TEN_MHZ_GCD);

    return Ticks;
}

UINT64 CPU_TicksToTime(UINT32 Ticks32)
{
    UINT64 Ticks;

    Ticks  = (UINT64)Ticks32 * (TEN_MHZ               /SLOW_CLOCKS_TEN_MHZ_GCD);
    Ticks /=                   (SLOW_CLOCKS_PER_SECOND/SLOW_CLOCKS_TEN_MHZ_GCD);

    return Ticks;
}

UINT64 CPU_MillisecondsToTicks(UINT64 Ticks)
{
    Ticks *= (SLOW_CLOCKS_PER_SECOND/SLOW_CLOCKS_MILLISECOND_GCD);
    Ticks /= (1000                  /SLOW_CLOCKS_MILLISECOND_GCD);

    return Ticks;
}

UINT64 CPU_MillisecondsToTicks(UINT32 Ticks32)
{
    UINT64 Ticks;

    Ticks  = (UINT64)Ticks32 * (SLOW_CLOCKS_PER_SECOND/SLOW_CLOCKS_MILLISECOND_GCD);
    Ticks /=                   (1000                  /SLOW_CLOCKS_MILLISECOND_GCD);

    return Ticks;
}

UINT64 CPU_MicrosecondsToTicks(UINT64 uSec) __attribute__ ((section ("SectionForFlashOperations")));
UINT64 CPU_MicrosecondsToTicks(UINT64 uSec)
{
#if ONE_MHZ <= SLOW_CLOCKS_PER_SECOND
    return uSec * (SLOW_CLOCKS_PER_SECOND / ONE_MHZ);
#else
    return uSec / (ONE_MHZ / SLOW_CLOCKS_PER_SECOND);
#endif
}

UINT32 CPU_MicrosecondsToTicks(UINT32 uSec) __attribute__ ((section ("SectionForFlashOperations")));
UINT32 CPU_MicrosecondsToTicks(UINT32 uSec)
{
#if ONE_MHZ <= SLOW_CLOCKS_PER_SECOND
    return uSec * (SLOW_CLOCKS_PER_SECOND / ONE_MHZ);
#else
    return uSec / (ONE_MHZ / SLOW_CLOCKS_PER_SECOND);
#endif
}

UINT32 CPU_MicrosecondsToSystemClocks(UINT32 uSec)
{
    uSec *= (SYSTEM_CLOCK_HZ/CLOCK_COMMON_FACTOR);
    uSec /= (ONE_MHZ        /CLOCK_COMMON_FACTOR);

    return uSec;
}

int CPU_MicrosecondsToSystemClocks(int uSec)
{
    uSec *= (SYSTEM_CLOCK_HZ/CLOCK_COMMON_FACTOR);
    uSec /= (ONE_MHZ        /CLOCK_COMMON_FACTOR);

    return uSec;
}

int CPU_SystemClocksToMicroseconds(int Ticks)
{
    Ticks *= (ONE_MHZ        /CLOCK_COMMON_FACTOR);
    Ticks /= (SYSTEM_CLOCK_HZ/CLOCK_COMMON_FACTOR);

    return Ticks;
}

UINT64 HAL_Time_CurrentTicks() __attribute__ ((section ("SectionForFlashOperations")));
UINT64 HAL_Time_CurrentTicks()
{
    UINT32 current_count = g_TIMER1LOAD - FM3_DTIM->TIMER1VALUE;
    return g_timer_count + (UINT64)current_count;
}

void Timer_UpdateLoadValue()
{
    UINT64 g_timer_diff;

    GLOBAL_LOCK(irq);
    if (g_target_count > g_timer_count) {
        g_timer_diff = g_target_count - g_timer_count;
        if (g_timer_diff >= (UINT64)MAX_TIMER_COUNT) {
            FM3_DTIM->TIMER1LOAD = (UINT32)MAX_TIMER_COUNT;
        } else {
            FM3_DTIM->TIMER1LOAD = (UINT32)g_timer_diff;
        }
    } else {
        // force interrupt
        FM3_DTIM->TIMER1LOAD = 2L;
    }
    g_TIMER1LOAD = FM3_DTIM->TIMER1LOAD;
}

void Timer_Interrupt(void *param) __attribute__ ((interrupt));
void Timer_Interrupt(void *param)
{
    FM3_DTIM->TIMER1INTCLR = 0;
    g_TIMER1VALUE = FM3_DTIM->TIMER1VALUE;
    g_timer_count += (UINT64)g_TIMER1LOAD;
    if (g_timer_count >= g_target_count) {
        HAL_COMPLETION::DequeueAndExec();
    } else {
        Timer_UpdateLoadValue();
    }
    if (g_timer_count >= (UINT64)MAX_TARGET_COUNT) {
        g_timer_count = (UINT64)0L;
    }
}

void HAL_Time_SetCompare(UINT64 CompareValue)
{
    GLOBAL_LOCK(irq);
    g_target_count = CompareValue;
    g_timer_count += (UINT64)(g_TIMER1LOAD - FM3_DTIM->TIMER1VALUE);
    if (g_timer_count >= (UINT64)MAX_TARGET_COUNT) {
        g_timer_count = (UINT64)0L;
    }
    Timer_UpdateLoadValue();
}

void CPU_INTC_Priority(UINT32 Irq_Index, UINT32 Irq_Pri);

BOOL HAL_Time_Initialize()
{
    g_timer_count = 0L;
    g_target_count = (UINT64)(-1L);
#if 0
    // Confirm that Watch Counter stops
    if ((FM3_WC->WCCR & WCCR_WCOP) != 0)
        FM3_WC->WCCR = 0;
    // Confirm that Watch Counter Prescaler stops
    if ((FM3_WC->CLK_EN & CLK_EN_CLK_EN_R) != 0)
        FM3_WC->CLK_EN = 0;
#endif
    FM3_DTIM->TIMER1LOAD = (UINT32)MAX_TIMER_COUNT;
    g_TIMER1LOAD = MAX_TIMER_COUNT;
    FM3_DTIM->TIMER1INTCLR = 0xffffffff;    // Clear interrupt flag with any value
    FM3_DTIM->TIMER1CONTROL &= ~0x80;       // Disable timer1 operation
    FM3_DTIM->TIMER1CONTROL =
            (DtimDev_INT_ENABLE  << 5)      // enable timer interrupt
            | (DtimDev_TIMER_PRE_16 << 2)   // prescale 1/16
            | (DtimDev_TIMER_SIZE_32 << 1)  // size 32bit
            | (DtimDev_FREERUN_MODE << 0);  // freerun mode
    BOOL ret = CPU_INTC_ActivateInterrupt(DTIM_QDU_IRQn, Timer_Interrupt, 0);
    CPU_INTC_Priority(DTIM_QDU_IRQn, 2);
    FM3_DTIM->TIMER1CONTROL |= 0x80;        // Enable timer1 operation
    return ret;
}

BOOL HAL_Time_Uninitialize()
{
    CPU_INTC_DeactivateInterrupt(DTIM_QDU_IRQn);
    FM3_DTIM->TIMER1CONTROL = 0;
    return TRUE;
}

//
// To calibrate this constant, uncomment #define CALIBRATE_SLEEP_USEC in TinyHAL.c
//
#define FM3_SLEEP_USEC_FIXED_OVERHEAD_CLOCKS 3

//void HAL_Time_Sleep_MicroSeconds(UINT32 uSec) __attribute__ ((section ("SectionForFlashOperations")));
void HAL_Time_Sleep_MicroSeconds(UINT32 uSec)
{
    GLOBAL_LOCK(irq);

    UINT32 current = HAL_Time_CurrentTicks();
    UINT32 maxDiff = CPU_MicrosecondsToTicks(uSec);

    if (maxDiff <= FM3_SLEEP_USEC_FIXED_OVERHEAD_CLOCKS) maxDiff  = 0;
    else maxDiff -= FM3_SLEEP_USEC_FIXED_OVERHEAD_CLOCKS;

    while (((INT32)(HAL_Time_CurrentTicks() - current)) <= maxDiff);
}

//#define SYSTEM_CYCLE_CLOCK_HZ    144000000    // defined in platform_selector.h
#define PERIOD    (SYSTEM_CYCLE_CLOCK_HZ/1000)  // 1ms
#define TIMEDELAY_CALIBRATION 40

// This function should be located on internal flash or SRAM.
// Assuming SYSTEM_CYCLE_CLOCK_HZ is 72MHz to 144MHz
void HAL_Time_Sleep_MicroSeconds_InterruptEnabled(UINT32 uSec)
{
    UINT32 CyclestoLoops = SYSTEM_CYCLE_CLOCK_HZ/1000000;
    CyclestoLoops *= uSec;
    if (CyclestoLoops <= TIMEDELAY_CALIBRATION)
        return;
    CyclestoLoops -= TIMEDELAY_CALIBRATION;
#ifdef MB9BF618T
    CyclestoLoops /= 3; // cycle count per iteration
#else
#if defined(MB9BF506N) || defined(MB9BF506R)
    CyclestoLoops /= 5; // cycle count per iteration
#else
#error "Err: CPU type not defined."
#endif
#endif
    if (!CyclestoLoops)
        return;

    __asm__ __volatile__ (
        " mov r3, %[loops]\n"
        " loop: subs r3, #1\n"
        " bne loop\n\n"
        : // No output registers
        : [loops] "r" (CyclestoLoops) // Input registers
        : "r3" // clobbered registers
    );
}

INT64 HAL_Time_TicksToTime(UINT64 Ticks)
{
    return CPU_TicksToTime(Ticks);
}

INT64 HAL_Time_CurrentTime()
{
    return CPU_TicksToTime(HAL_Time_CurrentTicks());
}

void HAL_Time_GetDriftParameters(INT32* a, INT32* b, INT64* c)
{
    *a = 1;
    *b = 1;
    *c = 0;
}
